import wx
import numpy as na
import numpy.numarray.mlab as mlab 
import random
import sys
import os 
import cPickle
#from IPython.Debugger import Tracer; debug_here = Tracer()
from CritPoint import CritPoint
import re

if( sys.platform == 'darwin' ):
    BIND_TEXT = True
else:
    BIND_TEXT = False
def create(parent):
    return A3D(parent)

class A3D(wx.Frame):
    def _init_coll_mnuFile_Items(self, parent, labelScheme):
        try:
            fm = parent.Append(-1, 'Open', '',wx.ITEM_NORMAL)
            self.Bind(wx.EVT_MENU,self.OnOpenFileMenu,fm)
            
            wp = parent.Append(-1,'Write PNG', '', wx.ITEM_NORMAL)
            self.Bind(wx.EVT_MENU,self.OnWritePNG,wp)

            wcp = parent.Append(-1,'Write Critical Points',
                     'Write Marked Critical Points to File',wx.ITEM_NORMAL)
            self.Bind(wx.EVT_MENU,self.OnWriteCPMenu,wcp)

            ocp = parent.Append(-1,'Open Critical Points',
                     'Open Marked Critical Points from File',wx.ITEM_NORMAL)
            self.Bind(wx.EVT_MENU,self.OnReadCPMenu,ocp)

            parent.AppendSeparator()

            for label in labelScheme:
                txtLabel = "%s (%s)"%(label,self.labelScheme.get(label,"Purple"))
                item = parent.AppendRadioItem(-1,txtLabel)
                wx.EVT_MENU(self,item.GetId(),self.setLabel)

            parent.AppendSeparator()

            toggle = parent.Append(-1,'Toggle Display\tctrl-t','',wx.ITEM_NORMAL)
            self.Bind(wx.EVT_MENU,self.ToggleDisplay,toggle)
            item = parent.Append(-1,"Toggle show all points\tctrl-a",'',wx.ITEM_NORMAL)
            self.Bind(wx.EVT_MENU,self.toggleshowAllPoints,item)
            item = parent.Append(-1,"Toggle focus points\tctrl-f",'',wx.ITEM_NORMAL)
            self.Bind(wx.EVT_MENU,self.toggleFocusPoints,item)
            self.reLabel = re.compile(r"""(?P<label>[a-zA-Z ]+)[\w]*\((?P<color>[a-zA-Z ]+)\)""")
        except Exception, error:
            print "failed in _init_coll_mnuFile_Items", error
            
    def winlevMenu(self, parent):
        """Create a menu for pre-specified window level values"""
        for label in self.winlevs.keys():
            item = parent.AppendRadioItem(-1,label)
            wx.EVT_MENU(self,item.GetId(),self.setPredefinedWinLev)


    def createOrientMenuItems(self, parent):
        """Create menu items for controlling the orientation of the displayed image"""
        r0 = parent.Append(-1, 'Flip LR','',wx.ITEM_NORMAL)
        self.Bind(wx.EVT_MENU,self.OnMnuRot_ud,r0)
        
        r1 = parent.Append(-1, 'Flip TB','',wx.ITEM_NORMAL)
        self.Bind(wx.EVT_MENU,self.OnMnuRot_lr,r1)
        
        r2 = parent.Append(-1, 'Transpose','',wx.ITEM_NORMAL)
        self.Bind(wx.EVT_MENU,self.OnMnuRot_trans,r2)

        r3 = parent.Append(-1, 'Flip Mask','',wx.ITEM_NORMAL)
        self.Bind(wx.EVT_MENU, self.OnMnuRot_flipMask, r3)

    def createColorMenuItems(self, parent):
        """Create menu items for controlling the coloring
        of the displayed image"""
        invert = parent.Append(-1, 'Invert Colors','', wx.ITEM_NORMAL)
        self.Bind(wx.EVT_MENU, self.OnMnuColor_inv, invert)

    def createScaleMenuItems(self, parent):
        """Create menu items for controlling the scaling
        of the displayed image"""
        s1 = parent.Append(-1, 'Reset\tctrl-=','',wx.ITEM_NORMAL)
        self.Bind(wx.EVT_MENU, self.ResetScale, s1)
        
        s2 = parent.Append(-1, 'Decrease\tctrl-shift-i','',wx.ITEM_NORMAL)
        self.Bind(wx.EVT_MENU, self.DecreaseScale, s2)

        s3 = parent.Append(-1, 'Increase\tctrl-i','',wx.ITEM_NORMAL)
        self.Bind(wx.EVT_MENU, self.IncreaseScale, s3)

    def createViewMenuItems(self, parent):
        """Create menu items for controlling the view orientation
        of the displayed image"""
        xy = parent.AppendRadioItem(-1, 'XY\tctrl-z','')
        self.Bind(wx.EVT_MENU, self.SetViewXY, xy)

        xz = parent.AppendRadioItem(-1, 'XZ\tctrl-y','')
        self.Bind(wx.EVT_MENU, self.SetViewXZ, xz)
        
        yz = parent.AppendRadioItem(-1, 'YZ\tctrl-x','')
        self.Bind(wx.EVT_MENU, self.SetViewYZ, yz)

    def createRenderingMenuItems(self, parent):
        """Create menu items for the volume rendering of the displayed image"""
        ps = parent.AppendRadioItem(-1, 'Local View\tctrl-s','')
        self.Bind(wx.EVT_MENU, self.SetSliceView, ps)

        pm = parent.AppendRadioItem(-1, 'Global View\tctrl-p','')
        self.Bind(wx.EVT_MENU, self.SetProjView, pm)

        parent.AppendSeparator()

        pmax = parent.AppendRadioItem(-1, 'maximum\tctrl-m','')
        self.Bind(wx.EVT_MENU, self.SetMaxView, pmax)

        pmin = parent.AppendRadioItem(-1, 'minimum\tctrl-shift-m','')
        self.Bind(wx.EVT_MENU, self.SetMinView, pmin)

        psum = parent.AppendRadioItem(-1, 'sumation\tctrl-u','')
        self.Bind(wx.EVT_MENU, self.SetSumView,psum)

    def createMenuBar1(self, parent):
        """Create menu bar for primary window"""

        parent.Append(menu=self.mnuFile, title='File')
        parent.Append(menu=self.mnuRot, title='Rotation')
        parent.Append(menu=self.mnuColor, title = 'Color')
        parent.Append(menu=self.mnuScale, title= 'Scale')
        parent.Append(menu=self.mnuView, title='View')
        parent.Append(menu=self.mnuProj, title='Proj')
        if( self.winlevs ):
            self.mnuWinLev = wx.Menu(title="Win/Lev")
            self.winlevMenu(self.mnuWinLev)
            parent.Append(menu=self.mnuWinLev, title='Win/Lev')


    def createMenusForMenuBar1(self,labelScheme):
        """Create individual menus for menu bar 1
        labelScheme: the labvel/color scheme for the annotation"""
        self.mnuFile = wx.Menu(title='File')
        self._init_coll_mnuFile_Items(self.mnuFile,labelScheme)
        
        self.mnuRot = wx.Menu(title="Rotation")
        self.createOrientMenuItems(self.mnuRot)
        
        self.mnuColor = wx.Menu(title="Color")
        self.createColorMenuItems(self.mnuColor)
        
        self.mnuScale = wx.Menu(title="Scale")
        self.createScaleMenuItems(self.mnuScale)
        
        self.mnuView = wx.Menu(title="View")
        self.createViewMenuItems(self.mnuView)
        
        self.mnuProj = wx.Menu(title="Proj")
        self.createRenderingMenuItems(self.mnuProj)
        
        self.menuBar1 = wx.MenuBar()
        
        self.createMenuBar1(self.menuBar1)


    def _init_ctrls(self, prnt, ttl = '',labelScheme=None):
        """ttl: title for the window
        labelScheme: label/color scheme for annotation"""
        try:
            if( not ttl ):
                ttl = repr(self.filename)+","+self.mode+","+repr(self.slice.shape)

        
            wx.Frame.__init__(self, id=-1, name='', parent=prnt,
                  style=wx.DEFAULT_FRAME_STYLE, size=wx.Size(
                    min(532,self.slice.shape[1]+20),
                    min(632,self.slice.shape[0]+90)),title=ttl)
            self.createMenusForMenuBar1(labelScheme)
            self.SetMenuBar(self.menuBar1)
            
            self.statusBar1 = wx.StatusBar(id=-1,
                  name='statusBar1', parent=self, style=0)
            
            #print "image size",self.slice.shape
            self.imageWindow = wx.ScrolledWindow(id=-1,
                  name='imageWindow', parent=self, pos=wx.Point(10, 24),
                  size=wx.Size(min(512,self.slice.shape[1]),
                               min(512,self.slice.shape[0])))
            self.imageWindow.SetVirtualSize(self.slice.shape[1::-1])
            self.imageWindow.SetScrollRate(10,10)
            self.imageWindow.EnableScrolling(True,True)
            self.imageWindow.SetCursor(wx.STANDARD_CURSOR)
            
            wx.EVT_LEFT_UP(self.imageWindow, self.OnScrolledwindow1LeftUp)
            wx.EVT_RIGHT_UP(self.imageWindow, self.OnScrolledwindow1RightUp)

            self.Bind(wx.EVT_RIGHT_UP, self.OnScrolledwindow1RightUp,
                      self.imageWindow)
            wx.EVT_PAINT(self.imageWindow, self.OnScrolledwindow1Paint)
            wx.EVT_SIZE(self.imageWindow, self.OnScrolledwindow1Size)
            
            # Set up Window and Level Sliders
            # set with dictionary stored values
            self.windowCtrl = wx.SpinCtrl(self,id=-1, name='windowCtrl') 
            self.windowCtrl.SetLabel('Window')
            self.Bind(wx.EVT_SPINCTRL, self.OnChangeWindow,self.windowCtrl)
            if( BIND_TEXT ):
                self.Bind(wx.EVT_TEXT, self.OnChangeWindow,self.windowCtrl)

            self.levelCtrl = wx.SpinCtrl(self,id=-1,name='levelCtrl')
            self.levelCtrl.SetLabel('Level')
            self.Bind(wx.EVT_SPINCTRL, self.OnChangeLevel, self.levelCtrl)
            if( BIND_TEXT ):
                self.Bind(wx.EVT_TEXT, self.OnChangeLevel, self.levelCtrl)
            

            self.cntrlThick = wx.SpinCtrl(self,id=-1, name='Thick')
            self.cntrlThick.SetLabel('Projection Thickness')
            self.cntrlThick.SetRange(0,100)
            self.cntrlThick.SetValue(0)
            self.Bind(wx.EVT_SPINCTRL, self.OnChangeThickness, self.cntrlThick)
            if( BIND_TEXT ):
                self.Bind(wx.EVT_TEXT, self.OnChangeThickness, self.cntrlThick)
            
            self.cntrlSlice = wx.SpinCtrl(self,id=-1, name="Slice")
            self.cntrlSlice.SetRange(0,self.nslices-1)
            self.cntrlSlice.SetValue(0)
            self.Bind(wx.EVT_SPINCTRL, self.OnChangeSlice, self.cntrlSlice)
            if( BIND_TEXT ):
                self.Bind(wx.EVT_TEXT, self.OnChangeSlice, self.cntrlSlice)            

            self.windowLabel=wx.StaticText(self, -1, ' window: ')
            self.levelLabel=wx.StaticText(self, -1, ' level: ')
            self.sliceLabel=wx.StaticText(self, -1,' slice: ')
            self.thickLabel=wx.StaticText(self, -1,' thickness: ')

            self.nextUnlabeled = wx.Button(self, id=-1, label="Next random")
            self.nextUnlabeled.Bind(wx.EVT_BUTTON, self.OnNextRandom)
            self.labelMessage = wx.StaticText(self, -1, '')
            self.pointMessage = wx.StaticText(self, -1, '')
            self.annotationMessage = wx.StaticText(self, -1, '')


        except Exception, error:
            print "failed in init_ctrls()", error

    def __init__(self, parent,
                 data = None,
                 mask = None,
                 grayFactor = None,
                 labelScheme = None,
                 labels = None,
                 winlevs = None,
                 **args):
        try:
            self.DEBUG = True
            self.randomSelect = False
            self.showAllPoints = True
            self.focusPoints = False
            self.AllowModifyPoints = args.get('AllowModifyPoints',False) 
            self.AllowAddPoints = args.get('AllowAddPoints',False)
            self.AllowDeletePoints = args.get('AllowDeletePoints',False)
            pointsFile = args.get('pointsFile',None)
            normalize = args.get('normalize',False)
            axis = args.get('axis',1)
            scls = args.get('scls',None)
            title = args.get('title','No Title')
            self.numRandom = int(args.get('numRandom',0))
            self.currentRandom = 0

            self.filename = ""
            if( not grayFactor ):
                grayFactor = [1]
            self.grayFactor = grayFactor
            self.axis = axis
            if( not mask ):
                mask = []
            self.mask = mask
            self.scale = 1
            self.absscls = scls
            if( not scls ):
               scls = [1,1,1]
            self.scls = na.array(scls)/min(scls) 
            self.sscl = [self.scls[0],self.scls[1]]
            self.zoom = 0
            self.slices = [0,0,0]
            self.plims = [0,1]
            self.view='xy'
            self.wslice = 0
            self.pt = 0
            self.writedir=os.getcwd()
            self.critpoints = []
            self.drawpoints = []
            self.mz = 1
            self.my = 1
            self.mx = 1
            self.mode = "L"
            self.FlipLR = False
            self.FlipUD = False
            self.projections = {'max':na.max,'min':na.min,'average':normSum}
            self.projs = {}
            self.cproj = na.max # current projection
            self.lproj = 'max'
            self.viewtype = 'slice'
            self.win=1
            self.lev=0

            self.displayAnno = True
            self.colors = ['Red','Green','Blue','Yellow','Orange','Purple']
            self.colorCount = 0
            if( pointsFile == None):
                self.pointsFile = "tmp.pckle"
            else:
                self.pointsFile = pointsFile
            if( pointsFile ):
                self.readCriticalPoints()
                
            if( not labels ):
                labels = ["unlabeled","labeled"]
            if( not labelScheme ):
                labelScheme = {"unlabeled":"Red","labeled":"Green"}
            self.labelScheme = labelScheme
            self.currentLabel = labels[0]
            self.winlevs = winlevs

            
            if( data == None ):
                return
            self.rdata = na.copy(data)
            self.filename = os.getcwd()
            self.mergeData()
            if( len(self.rdata[0].shape) != 3 ):
                self.nslices = 1
                
            self.getMips()
            self.SetView(None,draw=False)
            
             
            self._init_ctrls(parent,ttl=title,labelScheme=labels)
            self.__doLayout()
            self.wximg = wx.EmptyImage(self.slice.shape[1],self.slice.shape[0])
            self.SetWinLevelValues()
            self.winlevelimage()
            self.init_buffer()
            
            self.Show(True)
        except Exception, error:
            print "failed in A3D.__init__() ", error
            sys.exit()
    def __doLayout(self):           
            box1 = wx.BoxSizer(wx.VERTICAL)
            box2 = wx.BoxSizer(wx.VERTICAL)
            box2.Add(self.imageWindow,1,wx.ALL|wx.EXPAND,0)
            box2.Add(self.statusBar1,0,wx.ALL|wx.EXPAND,0)
            
            
            box3 = wx.BoxSizer(wx.VERTICAL)
            box3b = wx.BoxSizer(wx.HORIZONTAL)
            box3c = wx.BoxSizer(wx.HORIZONTAL) 
            box3d = wx.BoxSizer(wx.HORIZONTAL)
            box3b.Add(self.windowLabel,0,wx.ALIGN_LEFT,0)
            box3b.Add(self.windowCtrl,0,wx.ALIGN_RIGHT,0)
            
            box3c.Add(self.levelLabel,0,wx.ALIGN_LEFT,0)
            box3c.Add(self.levelCtrl,0,wx.ALIGN_RIGHT,0)
            box3d.Add(box3b,0)
            box3d.Add(box3c,1)
            box3.Add(box3d,0)
            box3e = wx.BoxSizer(wx.HORIZONTAL)
            box3f = wx.BoxSizer(wx.HORIZONTAL) 
            box3g = wx.BoxSizer(wx.HORIZONTAL)
            box3e.Add(self.sliceLabel,0,wx.ALIGN_LEFT,0)
            box3e.Add(self.cntrlSlice,0,wx.ALIGN_RIGHT,0)
            box3f.Add(self.thickLabel,0,wx.ALIGN_LEFT,0)
            box3f.Add(self.cntrlThick,0,wx.ALIGN_RIGHT,0)
            box3f2 = wx.BoxSizer(wx.HORIZONTAL)
            box3f2.Add(self.nextUnlabeled,0,wx.ALIGN_LEFT,0)
            box3f2.Add(self.labelMessage,0,wx.ALIGN_LEFT,0)
            box3h = wx.BoxSizer(wx.HORIZONTAL)
            box3g.Add(box3e,0)
            box3g.Add(box3f,1)
            box3h.Add(box3f2,1)
            box3i = wx.BoxSizer(wx.HORIZONTAL)
            box3i.Add(self.pointMessage,0,wx.ALIGN_LEFT,0)
            box3j = wx.BoxSizer(wx.HORIZONTAL)
            box3j.Add(self.annotationMessage, 0, wx.ALIGN_LEFT, 0)
            box3.Add(box3g, 0)
            box3.Add(box3h, 0)
            box3.Add(box3i, 0)
            box3.Add(box3j, 1)
            
            box1.Add(box3, 0, wx.EXPAND)
            box1.Add(box2,1,wx.EXPAND)
            
            self.SetAutoLayout(1)
            self.SetSizer(box1)

    def getMips(self):
        try:
            self.mips = {}
            self.minips = {}
            self.sums = {}
            self.projs = {}
            self.colorSettings = {}
            if( self.DEBUG ): 
                print "generating projection views"
            views = [('xy',0), ('xz',1), ('yz',2)]
            for i in range(len(self.data)):
                data = self.data[i]
                for pk in self.projections.keys():
                    cproj = self.projections[pk]
                    for view in views:
                        maxp = self.projs.get(pk,{})
                        tmp = maxp.get(view[0],[])
                        p = cproj(data,axis=view[1])
                        if( not self.colorSettings.has_key(pk) ):
                            vals = getWinLevValues(p)
                            self.colorSettings[pk] = vals
                            
                        tmp.append( p)
                        maxp[view[0]] = tmp
                        self.projs[pk] = maxp

                                   
        except Exception, error:
            print "failed in getMips", error
    def mergeData(self):
        """take self.rawData multiply by mask (if present) and concatenate along specified axis"""
        try:
            self.shape = self.rdata[0].shape
            self.data = []
            for i in range(len(self.rdata)):
                #print i
                d = self.rdata[i]
                #print "process mask"
                if( len(self.mask) == 1 ):
                    m = self.mask[0]
                elif( len(self.mask) > 1 ):
                    m = self.mask[i]
                else:
                    m = 1
                #print "process grayfactor",self.grayFactor
                try:
                    if( len(self.grayFactor) == 1 ):
                        gf = self.grayFactor[0]
                    elif( len(self.grayFactor) > 1 ):
                        gf = self.grayFactor[i]
                except:
                    gf = self.grayFactor
                try:
                    if( m == 1 and gf == 1 ):
                        dm = d
                    else:
                        dm = d * m * f
                except:
                    dm = d
                self.data.append(dm)


            self.nslices = self.shape[0]   
            self.mz = self.shape[0]-1
            self.my = self.shape[1]-1
            self.mx = self.shape[2]-1     
        except Exception, error:
            print "failed in mergeData", error
    def GetDataFromStandardImage(self, im ):
        try:
            if( im.mode == 'L'):
                self.slice = fromstring(im.tostring(), na.uint8)
                self.slice.shape = im.size[::-1]
                #print "shape when Mono",self.slice.shape
                self.mode = 'L'
            elif( im.mode == 'RGB' ):
                r, g, b = im.split()
                ra = fromstring(r.tostring(), na.uint8)
                ra.shape = r.size[::-1]
                ga = fromstring(g.tostring(), na.uint8)
                ga.shape = g.size[::-1]
                ba = fromstring(b.tostring(), na.uint8)
                ba.shape = b.size[::-1]
                self.slice = transpose(array([ra, ga, ba]), axes=[1,2,0])
                #print "shape when RGB",self.slice.shape[0:2]
                self.mode = 'RGB'
            else:
                self.mode = None
                self.slice = None
        except Exception, error:
            print "failed in GetDataFromStandardImage() ", error

    def OnOpenFileMenu(self, event):
        pass

    def win_lev(self, data, maxc = 255 ):
        try:
            print "self.lev",self.lev
            win = self.win
            lev = self.lev
            m = maxc / (2.0*win)
            o = m*(lev-win)
            return na.clip((m*data-o), 0, maxc).astype(na.uint8)
        except Exception, error:
            print "failed in win_lev() ", error
            print self.win, self.lev
    def winlevelimage(self):
        try:
            if( self.DEBUG ): print self.slice.max()
            temp = self.win_lev(self.slice)
            temp = na.transpose(na.array((temp, temp, temp), na.uint8),
                             (1, 2, 0))
            self.wximg.SetData( temp.tostring() )
        except Exception, error:
            print "failed in winlevelimage", error

    def SetWinLevelValues(self):
        try:
            if( self.DEBUG ): print "SetWinLevelValues"
            cv = self.colorSettings[self.lproj]
            self.windowCtrl.SetRange(cv['wl'], cv['wh'])
            self.windowCtrl.SetValue(cv['wc'])
            self.levelCtrl.SetRange(cv['ll'], cv['lh'])
            self.levelCtrl.SetValue(cv['lc'])
            self.win = cv['wc']
            self.lev = cv['lc']
            print "SetWinLevelValues self.lev", self.lev
        except Exception, error:
            print "failed in SetWinLevelValues", error

    def UpdateGeometryDefinitions(self):
        try:
            nd = [self.slice.shape[1], self.slice.shape[0]]
            if( self.DEBUG ): print "UpdateGeometry slice shape",nd
            self.wximg = wx.EmptyImage(nd[0], nd[1])
        except Exception, error:
            print "failed in UpdateGeometryDefinitions()", error
            pass
    def setPredefinedWinLev(self, event):
        b = event.GetEventObject()
        items = b.GetMenuItems()
        for item in items:
            if item.IsCheckable() and item.IsChecked() :
                label = item.GetLabel()
                break
        wl = self.winlevs[label]
        self.win = wl['win']
        print type(self.win)
        self.lev = wl['lev']
        self.winlevelimage()
        self.init_buffer()
        
    def OnChangeWindow(self, event):
        self.win = self.windowCtrl.GetValue()
        self.winlevelimage()
        self.init_buffer()

    def OnChangeLevel(self, event):
        self.lev = self.levelCtrl.GetValue()
        self.winlevelimage()
        self.init_buffer()

    def OnChangeSlice(self, event):
        # need to change this so the value updates correctly when using the
        # keyboard arrows
        self.wslice = self.cntrlSlice.GetValue()
        if( self.DEBUG ): print self.wslice
        #self.ProjectSlab()
        #self.DrawCritPoints()
        #self.winlevelimage()
        self.SetView(event)
        self.winlevelimage()
        self.init_buffer()

    def OnNextRandom(self, event):
        self.randomSelect = True
        self.currentNumCritpoints = len(self.critpoints)
        randomView = random.choice([1, 2, 3])
        rlabel = random.choice(self.labelScheme.keys())
        self.labelMessage.SetLabel(" %s "%rlabel)
        self.setLabelMenu(rlabel)
        rx = random.randint(0, self.mx)
        ry = random.randint(0, self.my)
        rz = random.randint(0, self.mz)
        if(randomView == 1 ):
            self.SetViewXY(event)
        elif( randomView == 2 ):
            self.SetViewXZ(event)
        else:
            self.SetViewYZ(event)
        print "OnNextRandom"
        if( self.view == 'xy' ):
            self.cntrlSlice.SetValue(rz)
            self.OnChangeSlice(event)
        elif( self.view == 'xz' ):
            self.cntrlSlice.SetValue(ry)
            self.OnChangeSlice(event)
        else:
            self.cntrlSlice.SetValue(rx)
            self.OnChangeSlice(event)
        self.currentRandom += 1
        if( self.currentRandom > self.numRandom ):
            print "finished random annotation"
            sys.exit(0)

    def OnChangeThickness(self, event):
        self.pt = self.cntrlThick.GetValue() # projection thickness
        self.OnChangeSlice(event)

    def ProjectSlab(self):
        #print "slice=",self.wslice
        try:
            self.dataSlices = []
            if( self.view == 'xy' ):
                self.slices[0] = self.wslice
                self.plims = [max(0, self.wslice-self.pt),
                              min(self.shape[0], self.wslice+self.pt+1)]
                for d in self.data:
                    self.dataSlices.append(
                        self.cproj(d[self.plims[0]:self.plims[1], :, :], 0))
            elif( self.view == 'xz' ):
                self.slices[1] = self.wslice
                self.plims = [max(0, self.wslice-self.pt),
                              min(self.shape[1], self.wslice+self.pt+1)]
                for d in self.data:
                    self.dataSlices.append(
                        self.cproj(d[:, self.plims[0]:self.plims[1], :], 1))
            else:
                self.slices[2] = self.wslice
                self.plims = [max(0, self.wslice-self.pt),
                              min(self.shape[2], self.wslice+self.pt+1)]
                for d in self.data:
                    self.dataSlices.append(
                        self.cproj(d[:, :, self.plims[0]:self.plims[1]], 2))
            self.makeSlice()
        except Exception, error:
            print "failed in ProjectSlab", error
    def makeSlice(self):
        self.slice = na.concatenate(self.dataSlices, axis=self.axis)
        self.slice = self.slice[::-1, :]
        if(self.FlipLR):
            if( self.mode == 'L' ):
                self.slice = self.slice[::-1, :]
            elif( self.mode == 'RGB' ):
                self.slice = self.slice[:, ::-1, :]
        if(self.FlipUD):
            if( self.mode == 'L' ):
                self.slice = self.slice[:, ::-1]
            elif( self.mode == 'RGB' ):
                self.slice = self.slice[:, :, ::-1]
        if( self.DEBUG ): print "makeSlice", self.slice.shape
    def OnWritePNG(self,  event):
        dlg = wx.FileDialog(self, "Write to file", self.writedir,
                            "", "PNG files (*.png)|*.png", wx.SAVE)
        if dlg.ShowModal() == wx.ID_OK:
            filename = dlg.GetPath()
        else:
            filename = 'dummy.png'
    
        self.writedir = os.path.dirname(filename)
        
        temp = wx.ImageFromBitmap(self.buffer)
        temp.SaveFile(filename, wx.BITMAP_TYPE_PNG)

    def setLabelMenu(self, label):
        try:
            print "setLabelMenu", label
            items = self.mnuFile.GetMenuItems()
            for item in items:
                print item.GetLabel()
                if label in item.GetLabel():
                    item.Check()
                    self.currentLabel = label
                    break
        except Exception, error:
            print "failed in setLabelMenu", error
    def setLabel(self, event):
        """There must be a better way of doing this"""
        b = event.GetEventObject()
        items = b.GetMenuItems()
        for item in items:
            if( item.IsCheckable() and item.IsChecked() ):
                currentLabel = item.GetLabel()
                for label in self.labelScheme.keys():
                    if label in currentLabel:
                        self.currentLabel = label
                        break
        #debug_here()
    def OnWriteCPMenu(self, event):
        """Write critical points to file"""
        dlg = wx.FileDialog(self, "Write Critical Points to file",
            self.writedir, "",
            "pckle files (*.pckle)|*.pckle|All files (*.*)|*.*", wx.SAVE)
        if dlg.ShowModal() == wx.ID_OK:
            self.pointsFile = dlg.GetPath()
        self.writeCriticalPoints() 

    def ToggleDisplay(self, event):
        self.displayAnno = not self.displayAnno
        self.init_buffer()
    def toggleshowAllPoints(self, event):
        self.showAllPoints = not self.showAllPoints
        self.DrawCritPoints()
        self.init_buffer()
    def toggleFocusPoints(self, event):
        self.focusPoints = not self.focusPoints
    def OnReadCPMenu(self, event):
        """read critical points from file. At this point the function reads in a
        cPickle file and extends the current critpoints attribute. No error
        checking is done regarding duplicate poitns"""
        try:
            dlg = wx.FileDialog(self, "Open Critical Points from file", self.writedir, "", "*.*", wx.OPEN)
            if dlg.ShowModal() == wx.ID_OK:
                self.pointsFile = dlg.GetPath()
            self.readCriticalPoints() 
            self.DrawCritPoints()
            self.winlevelimage()
        except Exception, error:
            print "failed in open critical points", error
            event.Skip()
    def writeCriticalPoints(self):
        try:
            fo = open(self.pointsFile,"wb")
            cPickle.dump(self.critpoints,fo)
            fo.close()
        except Exception, error:
            print "failed in writeCriticalPoints", error
    def readCriticalPoints(self):
        try:
            self.writedir = os.path.dirname(self.pointsFile)
            fo = open(self.pointsFile,"rb")
            newPoints = cPickle.load(fo)

            self.critpoints = newPoints
            fo.close()
        except Exception, error:
            print "failed in readCriticalPoints", error
    def findNearestCritPoint(self,p,verbose=True):
        try:
            if( not self.critpoints ):
                return None, None, None
            minind = 0 
            minpoint = self.critpoints[0]
            mindist = (minpoint[0]-p[0])**2+(minpoint[1]-p[1])**2+(minpoint[2]-p[2])**2
            # make sure points are within the current view
            for i in range(1,len(self.critpoints)):
                point = self.critpoints[i]
                dist = (point[0]-p[0])**2+(point[1]-p[1])**2+(point[2]-p[2])**2 
                #print "selectedPoint: %s; currentPoint: %s; current dist: %f; minimum dist: %f"%(p, point, dist, mindist)
                if( dist < mindist ):
                    mindist = dist
                    minind = i
                    minpoint = point
            if( verbose ):
                print minind, minpoint, mindist
            return minind, minpoint, mindist
        except Exception, error:
            print "failed in findNearestCritPoint", error
            return None, None, None

    def delete_nearest_critpoint(self, p ):
        try:
            minind, minpoint, mindist = self.findNearestCritPoint(p)
            if( minind != None ):
                del self.critpoints[minind]
            return minpoint
        except Exception, error:
            print "failed in delete nearest_drawpointXY",error

    def getPoint(self,event):
        try:
            self.x, self.y = event.GetPositionTuple()
            scaling = self.scale * na.array(self.sscl)
            x0=self.imageWindow.GetViewStart()
            xs = self.imageWindow.GetScrollPixelsPerUnit()
            xx = int((self.x+x0[0]*xs[0])/scaling[0])
            yy = int((self.y+x0[1]*xs[1])/scaling[1]) #self.slice.shape[1]
            p = CritPoint()
            if( self.view == 'xy' ):
                if( self.FlipUD ):
                    xx = self.mx - xx
                if( not self.FlipLR ):
                    yy = self.my - yy
                zz = na.argmax(self.data[0][self.plims[0]:self.plims[1],yy,xx])+self.plims[0]
                p.extend([xx,yy,zz])
            elif( self.view == 'xz' ):
                yy = self.mz - yy
                zz = na.argmax(self.data[0][yy,self.plims[0]:self.plims[1],xx])+self.plims[0]
                p.extend([xx,zz,yy])
            else:
                yy = self.mz - yy
                zz = na.argmax(self.data[0][yy,xx,self.plims[0]:self.plims[1]])+self.plims[0]
                p.extend([zz,xx,yy])
            return p
        except IndexError, error:
            print "invalid index"
    def modifyNearestCritPoint(self,p):
        minind, minpoint, mindist = self.findNearestCritPoint(p)
        if( minind == None ):
            return
        self.critpoints[minind].setLabel(self.currentLabel)
        txt = "%s (%s)"%(self.critpoints[minind],self.data[0][p[2],p[1],p[0]])
        self.pointMessage.SetLabel(txt)
        self.DrawCritPoints()
        self.init_buffer()
    def focusNearestCritPoint(self,event, p):
        self.cntrlThick.SetValue(0)
        self.pt = self.cntrlThick.GetValue()
        minind, minpoint, mindist = self.findNearestCritPoint(p)
        if( minind == None ):
            return
        if( self.view == 'xy'):
            self.cntrlSlice.SetValue(minpoint[2])
        elif( self.view == 'xz'):
            self.cntrlSlice.SetValue(minpoint[1])
        else:
            self.cntrlSlice.SetValue(minpoint[0])
        self.OnChangeSlice(event)
    def OnScrolledwindow1RightUp(self, event):
        try:
            print self.AllowModifyPoints, self.AllowDeletePoints
            p = self.getPoint(event)
            if( not p ):
                return
            
            modified = False
            if( not event.m_shiftDown and self.AllowModifyPoints ):
                self.modifyNearestCritPoint(p)
                modified = True

            elif( event.m_shiftDown and self.AllowDeletePoints ):
                if( self.DEBUG ): print "delete"
                self.delete_nearest_critpoint(p)
                modified = True
            if( modified and self.critpoints ):
                self.writeCriticalPoints()
            self.DrawCritPoints()
            self.init_buffer()
        except Exception, error:
            print "failed in OnScrolledwindow1RightUp", error
    def OnScrolledwindow1LeftUp(self, event):
        try:
            p = self.getPoint(event)
            if( not p ):
                return
            p.setLabel(self.currentLabel)
            if( event.m_shiftDown and self.AllowAddPoints ):
                self.critpoints.append(p)
                modified = True
                ptxt = "%s (%s)"%(p,self.data[0][p[2],p[1],p[0]])
                atxt = "%d points;"%len(self.critpoints)
                if( self.randomSelect ):
                    newPoints = len(self.critpoints)-self.currentNumCritpoints
                    atxt += " %d points this random sel."%newPoints
                self.updateMessages(pointTxt=ptxt,annotationTxt=atxt)
            
                self.writeCriticalPoints()
                self.DrawCritPoints()
                self.init_buffer()
            else:
                if( not self.focusPoints ):
                    txt = "%s (%s)"%(p,self.data[0][p[2],p[1],p[0]])
                    self.updateMessages(pointTxt=txt)
                else:
                    self.focusNearestCritPoint(event, p)
        except Exception, error:
            print "Failed in OnScrolledwindow1LeftUp", error
    def DrawAllCritPoints(self):
        self.drawpoints = self.critpoints

    def updateMessages(self,pointTxt='', annotationTxt=''):
        if( pointTxt):
            self.pointMessage.SetLabel(pointTxt)
        if( annotationTxt ):
            self.annotationMessage.SetLabel(annotationTxt)
    def DrawCritPointsXY(self):
        """Identify points to display within the current XY view"""
        self.drawpoints = []
        for point in self.critpoints:
            if( self.plims[0] <= point[2] < self.plims[1] ):
                if( self.showAllPoints or point.getLabel() == self.currentLabel ):
                    self.drawpoints.append(point)

    def DrawCritPointsXZ(self):
        """Identify points to display within the current XZ view"""
        self.drawpoints = []
        for point in self.critpoints:
            if( self.plims[0] <= point[1] < self.plims[1] ):
                if( self.showAllPoints or point.getLabel() == self.currentLabel ):
                    self.drawpoints.append(point)
      
    def DrawCritPointsYZ(self):
        """Identify points to display within the current YZ view"""
        self.drawpoints = []
        for point in self.critpoints:
            if( self.plims[0] <= point[0] < self.plims[1] ):
                if( self.showAllPoints or point.getLabel() == self.currentLabel ):
                    self.drawpoints.append(point)  
    def DrawCritPoints(self):

        if( self.view == 'xy' ):
            self.DrawCritPointsXY()
        elif( self.view == 'xz' ):
            self.DrawCritPointsXZ()
        else:
            self.DrawCritPointsYZ()

    def OnMnuColor_inv(self, event):
        if( self.mode == 'L'):
            self.slice = mlab.max(ravel(self.slice))-self.slice
        elif( self.mode == 'RGB' ):
            self.slice[:,:,0] = \
                mlab.max(ravel(self.slice[:,:,0]))-self.slice[:,:,0]
            self.slice[:,:,1] = \
                mlab.max(ravel(self.slice[:,:,1]))-self.slice[:,:,1]
            self.slice[:,:,2] = \
                mlab.max(ravel(self.slice[:,:,2]))-self.slice[:,:,2]
        else:
            return
        self.winlevelimage()
        self.init_buffer()
    def ResetScale(self, event):
        self.scale = 1
        self.zoom = 1
        self.init_buffer()

    def DecreaseScale(self, event):
        try:
            self.scale = max(1,self.scale-1)
            self.zoom = 1
            self.init_buffer()
        except:
            event.Skip()
    def IncreaseScale(self, event):
        try:
            self.scale = self.scale+1
            self.zoom = 1
            self.init_buffer()
        except:
            event.Skip()
    def SetSliceView(self, event):
        try:
            self.viewtype = 'slice'
            self.SetView(event)
            self.winlevelimage()
            self.init_buffer()
        except:
            event.Skip()
    def SetProjView(self, event):
        self.viewtype = 'projection'
        self.SetView(event)
        self.winlevelimage()
        self.init_buffer()
    def SetView(self, event,draw=True):
        try:
            self.cproj = self.projections[self.lproj]
            if( self.viewtype == 'projection' ):
                mips = self.projs[self.lproj]
                self.dataSlices = []
                for m in mips[self.view]:
                    self.dataSlices.append(m)
        
                if( self.view == 'xy' ):
                    self.plims=[0,self.shape[0]]
                elif( self.view == 'xz' ):
                    self.plims = [0,self.shape[1]]
                else:
                    self.plims = [0,self.shape[2]]
                self.makeSlice()
                self.DrawAllCritPoints()
            else:
                self.ProjectSlab()
                self.DrawCritPoints()
        except Exception, error:
            print "failed in SetView", error

    def SetMaxView(self, event):
        try:
            self.colorSettings[self.lproj]['wc'] = self.windowCtrl.GetValue()
            self.colorSettings[self.lproj]['lc'] = self.levelCtrl.GetValue()
            self.lproj = 'max'
            self.SetView(event)
            self.SetWinLevelValues()
            self.winlevelimage()
            self.init_buffer()
        except:
            event.Skip()
    def SetMinView(self, event):
        try:
            self.colorSettings[self.lproj]['wc'] = self.windowCtrl.GetValue()
            self.colorSettings[self.lproj]['lc'] = self.levelCtrl.GetValue()
            self.lproj = 'min'
            self.SetView(event)
            self.SetWinLevelValues()
            self.winlevelimage()
            self.init_buffer()
        except:
            event.Skip()
    def SetSumView(self,event):
        try:
            self.colorSettings[self.lproj]['wc'] = self.windowCtrl.GetValue()
            self.colorSettings[self.lproj]['lc'] = self.levelCtrl.GetValue()
            self.lproj = 'average'
            self.SetView(event)
            self.SetWinLevelValues()
            self.winlevelimage()
            self.init_buffer()
        except:
            pass
    def SetViewXY(self, event):
        try:
            self.view = 'xy'
            self.sscl = [self.scls[0],self.scls[1]]
            self.zoom = 1
            self.cntrlSlice.SetRange(0,self.shape[0]-1)
            self.wslice = self.slices[0]
            self.cntrlSlice.SetValue(self.wslice)
            #self.ProjectSlab()
            self.SetView(event)
            self.UpdateGeometryDefinitions()
            self.winlevelimage()
            self.init_buffer()
        except Exception, error:
            event.Skip()
    def SetViewXZ(self, event):
        try:
            self.view = 'xz'
            self.sscl = [self.scls[0],self.scls[2]]
            self.zoom = 1
            self.cntrlSlice.SetRange(0,self.shape[1]-1)
            self.wslice = self.slices[1]
            self.cntrlSlice.SetValue(self.wslice)
            self.SetView(event)
            self.UpdateGeometryDefinitions()
            self.winlevelimage()
            self.init_buffer()
        except Exception, error:
            print "failed in SetViewXZ",error
            event.Skip()
    def SetViewYZ(self, event):
        try:
            self.view = 'yz'
            self.sscl = [self.scls[1],self.scls[2]]
            self.zoom = 1
            self.cntrlSlice.SetRange(0,self.shape[2]-1)
            self.wslice = self.slices[2]
            self.cntrlSlice.SetValue(self.wslice)
            self.SetView(event)
            self.UpdateGeometryDefinitions()
            self.winlevelimage()
            self.init_buffer()
        except Exception, error:
            print "failed in SetViewYZ", error
            event.Skip()

    def OnMnuRot_ud(self, event):
        try:
            self.FlipUD = not self.FlipUD
            self.makeSlice()
            self.winlevelimage()
            self.init_buffer()
        except:
            event.Skip()

    def OnMnuRot_lr(self, event):
        try:
            self.FlipLR = not self.FlipLR
            self.makeSlice()
            self.winlevelimage()
            self.init_buffer()
        except:
            event.Skip()

    def OnMnuRot_trans(self, event):
        try:
            if( self.mode == 'L'):
                self.slice = transpose(self.slice)
            elif( self.mode == 'RGB' ):
                self.slice= transpose(self.slice,axes=[1,0,2])
            self.zoom = 1
            self.winlevelimage()
            self.init_buffer()
        except:
            event.Skip()
    def OnMnuRot_flipMask(self, event):
        try:
            if( self.mode == 'L'):
                self.slice = transpose(self.slice)
            elif( self.mode == 'RGB' ):
                self.slice= transpose(self.slice,axes=[1,0,2])
            self.zoom = 1
            self.winlevelimage()
            self.init_buffer()
        except:
            event.Skip()
    def OnScrolledwindow1Paint(self, event):
        try:
            self.init_buffer()
        except:
            pass
 
    def OnScrolledwindow1Size(self, event):
        if( self.GetAutoLayout() ):
            self.Layout()
        else:
            event.Skip()
    def getColor(self,label):
        print self.labelScheme
        print "requested label",repr(label), self.currentLabel,self.labelScheme.get(label,"red"),type(label),type(self.labelScheme.keys()[0]),label in self.labelScheme.keys()
        return self.labelScheme.get(label,"red")
    def init_buffer(self):
        try:
            scaling = self.scale * na.array(self.sscl)
            w = self.wximg.GetWidth()*scaling[0]
            h = self.wximg.GetHeight()*scaling[1]
            self.imageWindow.SetVirtualSize((w,h))

            self.buffer = wx.BitmapFromImage(self.wximg.Scale(w,h))
            cdc = wx.ClientDC(self.imageWindow)
            self.imageWindow.PrepareDC(cdc)
            dc = wx.BufferedDC(cdc,self.buffer)
            if( self.DEBUG ): print "# drawwpoints",len(self.drawpoints) 
            if(len(self.drawpoints) > 0 and self.displayAnno ):
                scaling = self.scale * na.array(self.sscl)
                
                for point in self.drawpoints:
                    pen = wx.Pen(wx.NamedColour(self.getColor(point.getLabel())),1,wx.SOLID)
                    brush = wx.Brush(wx.NamedColour(self.getColor(point.getLabel())))
                    dc.SetPen(pen)
                    dc.SetBrush(brush)
                    p = [point[0],point[1],point[2]]
                    if( self.view == 'xy' ):
                        if(self.FlipLR): p[1] = self.my-p[1]
                        if(self.FlipUD): p[0] = self.mx-p[0]
                        crds = [int(p[0]*scaling[0]+0.5),int((self.my-p[1])*scaling[1]+0.5)]
                        #sz = [int(scaling[0]+0.5),int(scaling[1])+0.5)]
                        #dc.DrawRectangle(int(p[0]*scaling[0]+0.5),\
                                #int((self.my-p[1])*scaling[1]+0.5),
                        #int(scaling[0]+0.5),int(scaling[1]+0.5))
                    if( self.view == 'xz' ):
                        if(self.FlipLR): p[2] = self.mz-p[2]
                        if(self.FlipUD): p[0] = self.mx-p[0]
                        crds = [int(p[0]*scaling[0]+0.5),int((self.mz-p[2])*scaling[1]+0.5)]
                    if( self.view == 'yz' ):
                        if(self.FlipLR): p[2] = self.mz-p[2]
                        if(self.FlipUD): p[1] = self.my-p[1]            
                        crds = [int(p[1]*scaling[0]+0.5),int((self.mz-p[2])*scaling[1]+0.5)]
                    sz = [max(2,int(scaling[0]+0.5)),max(2,int(scaling[1]+0.5))]
                    dc.DrawRectangle(crds[0],crds[1],sz[0],sz[1])
        
        except Exception, error:
            print "failed in init_buffer", error


def normSum(data,axis=0):
    mx = data.max()
    d = na.sum(data-data.min(),axis=axis)
    temp= 10*na.log(na.abs(d)+1)
    print temp.min(),temp.max()
    return temp
def getLabels(filename):
    try:
        fo = open(filename,'r')
        labels = fo.readlines()
        labels = [ l.strip() for l in labels ]
        return labels
    except Exception, error:
        print "failed in getLabelScheme", error
def getColors(filename):
    try:
        fo = open(filename,'r')
        colors = fo.readlines()
        colors = [ c.strip() for c in colors ]
        return colors
    except Exception, error:
        print "failed in getColors", error
def getWinLevelSettings(filename):
    try:
        wls = {}
        fo = open(filename,'r')
        data = fo.readlines()
        for d in data[1:]:# first line is header
            dd = d.split(',')
            wls[dd[0]] = {'win':int(dd[1]),'lev':int(dd[2])}
        return wls
    except Exception, error:
        print "failed in getWinLevelSettings", error
def getLabelScheme(labels, colors):
    try:
        colorScheme = {}
        # always use the first color uniquely for the first label
        colorScheme[labels[0]] = colors[0]

        numColors = len(colors)-1
        count = 0
        for l in labels[1:]:
            colorScheme[l] = colors[count % numColors +1]
            count += 1
        return colorScheme
    except Exception, error:
        print "failed in getLabelScheme", error
def getWinLevValues(data):
    """for the given set of data, compute win/lev values"""
    imax = data.max()
    imin = data.min()
    wh = (imax-imin)
    wl = 1
    lh = (imax+imin)/2+(imax-imin)-1
    ll = (imax+imin)/2-(imax-imin)
    vals = {'ll':ll,'lh':lh,'wh':wh,'wl':wl,'wc':(wl+wh/3),'lc':(ll+lh)/2}
    return vals

def call( data = None, gFactor=[1], clp = None, mask=[],
          labelFile=None,colorFile=None, labels=None, colors = None, **args ):
    """create the wxframe and run the application"""
    try:
        if( labels == None ):
            labels=["unlabeled","labeled"]
        if( colors == None ):
            colors=["red","green"]
        winlevs = []
        app = wx.PySimpleApp()
        wx.InitAllImageHandlers()
        if( data == None ):
            return
       
        if( labelFile ):
            labels = getLabels(labelFile)
        if( colorFile ):
            colors = getColors(colorFile)
        winlevFile = args.get("winlevFile",None)
        print "winlevFile",winlevFile
        if( winlevFile ):
            winlevs = getWinLevelSettings(winlevFile)

        labelScheme = getLabelScheme(labels,colors)
        dataIn = []
        for i in range(len(data)):
            if( clp ):
                print "clipping with",clp
                dataIn.append(na.clip(data[i],clp[0],clp[1]))
            else:
                dataIn.append(na.copy(data[i]))

        frame = A3D(None,data=dataIn,mask=mask,
                    grayFactor = gFactor, 
                    labelScheme = labelScheme, labels=labels, 
                    winlevs= winlevs, **args)
        app.MainLoop()
    except Exception, error:
        print "failed in a3d.call()", error

if __name__ == '__main__':
    call()
